package com.kk.xhr.admin.biz.impl;

import com.kk.xhr.admin.biz.ISalaryBizService;
import com.kk.xhr.admin.controller.request.SalaryAddRequest;
import com.kk.xhr.admin.controller.request.SalaryModifyRequest;
import com.kk.xhr.admin.controller.request.SalaryQueryRequest;
import com.kk.xhr.admin.controller.vo.SalaryVO;
import com.kk.xhr.admin.mail.MailService;
import com.kk.xhr.admin.mail.SalaryEmail;
import com.kk.xhr.common.ErrorCodeEnum;
import com.kk.xhr.common.exception.BusinessException;
import com.kk.xhr.common.pojo.BinaryEnum;
import com.kk.xhr.model.dto.PageDto;
import com.kk.xhr.model.dto.RewardDTO;
import com.kk.xhr.model.dto.SalaryDTO;
import com.kk.xhr.model.dto.UserInfoDTO;
import com.kk.xhr.model.service.IRewardService;
import com.kk.xhr.model.service.ISalaryService;
import com.kk.xhr.model.service.ISignService;
import com.kk.xhr.model.service.IUserService;
import com.kk.xhr.util.DateUtil;
import com.kk.xhr.web.vo.PageVO;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * 薪资biz服务接口实现
 *
 * @author kkmystery
 * @version 1.0 2021/3/7
 * @since 1.0.0
 */
@Service
@RequiredArgsConstructor
public class SalaryBizServiceImpl implements ISalaryBizService {
    private final ISalaryService salaryService;
    private final IUserService userService;
    private final ISignService signService;
    private final MailService mailService;
    private final IRewardService rewardService;

    /**
     * {@inheritDoc}
     */
    @Override
    public PageVO<SalaryVO> queryPage(SalaryQueryRequest request) {
        PageDto<SalaryDTO> dtos = salaryService.queryPage(request.getUserId(), request.getLimit(), request.getOffset());
        if (0 == dtos.getTotal()) {
            return PageVO.empty();
        }
        return PageVO.fromDTO(dtos, this::generateVO);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public SalaryVO queryMine(Long userId) {
        SalaryDTO dto = salaryService.queryByUserId(userId)
            .orElseThrow(() -> new BusinessException(ErrorCodeEnum.SALARY_NOT_EXIST));
        return this.generateVO(dto);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List<SalaryVO> queryLog(Long userId) {
        return salaryService.queryLog(userId)
            .stream()
            .map(this::generateVO)
            .collect(Collectors.toList());
    }

    /**
     * 查询某个用户薪资
     *
     * @param userId 用户id
     * @return SalaryVO
     */
    @Override
    public SalaryVO query(Long userId) {
        SalaryDTO dto = salaryService.queryByUserId(userId)
            .orElseThrow(() -> new BusinessException(ErrorCodeEnum.SALARY_NOT_EXIST));
        return this.generateVO(dto);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean add(SalaryAddRequest request) {
        Optional<SalaryDTO> salaryDTO = salaryService.queryByUserId(request.getUserId());
        if (salaryDTO.isPresent()) {
            throw new BusinessException(ErrorCodeEnum.SALARY_ALREADY_EXIST);
        }
        return salaryService.add(SalaryDTO.builder()
            .userId(request.getUserId())
            .basicSalary(request.getBasicSalary())
            .pensionBase(request.getPensionBase())
            .pensionPercentage(request.getPensionPercentage())
            .medicalBase(request.getMedicalBase())
            .medicalPercentage(request.getMedicalPercentage())
            .accumulationFundBase(request.getAccumulationFundBase())
            .accumulationFundPercentage(request.getAccumulationFundPercentage())
            .individualTax(request.getIndividualTax())
            .salaryTime(DateUtil.validTime(request.getSalaryTime()))
            .build());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean modify(Long id, SalaryModifyRequest request) {
        return salaryService.modify(SalaryDTO.builder()
            .id(id)
            .basicSalary(request.getBasicSalary())
            .pensionBase(request.getPensionBase())
            .pensionPercentage(request.getPensionPercentage())
            .medicalBase(request.getMedicalBase())
            .medicalPercentage(request.getMedicalPercentage())
            .accumulationFundBase(request.getAccumulationFundBase())
            .accumulationFundPercentage(request.getAccumulationFundPercentage())
            .individualTax(request.getIndividualTax())
            .salaryTime(DateUtil.validTime(request.getSalaryTime()))
            .build());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean modifyStatus(Long id, BinaryEnum status) {
        return salaryService.modifyStatus(id, status);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void calculateSalary() {
        List<UserInfoDTO> userList = userService.query();
        List<SalaryEmail> salaryEmailList = new ArrayList<>(userList.size());
        Pair<LocalDateTime, LocalDateTime> mouth = DateUtil.getMouth();
        LocalDate time = LocalDate.now().minusMonths(1L);
        LocalDateTime now = LocalDateTime.now();
        userList.forEach(user -> {
            // 计算考勤时长(签到)
            Pair<Long, Long> signTime = signService.queryMouthSign(user.getId(), mouth);
            salaryService.queryByUserId(user.getId())
                .ifPresent(salary -> {
                    List<RewardDTO> rewards = rewardService.queryByUserId(user.getId());
                    salary.setCreated(null);
                    salary.setUpdated(null);
                    salaryService.add(salary);
                    // 计算薪资
                    float accumulation = salary.getAccumulationFundBase() + salary.getBasicSalary() * salary.getAccumulationFundPercentage() / 100;
                    float medical = salary.getMedicalBase() + salary.getBasicSalary() * salary.getMedicalPercentage() / 100;
                    float pension = salary.getPensionBase() + salary.getBasicSalary() * salary.getPensionPercentage() / 100;
                    long bonus = rewards.stream().map(RewardDTO::getMoney).count();
                    float finalSalary = salary.getBasicSalary() * (signTime.getLeft() / (signTime.getLeft() + signTime.getRight()))
                         - accumulation - medical - pension + bonus;
                    // 获取奖惩信息
                    salary.setFinalSalary((long) finalSalary);
                    salary.setAbsenceDay(signTime.getRight().intValue());
                    salary.setWorkTime(signTime.getLeft().doubleValue());
                    salary.setSalaryTime(now);
                    salary.setStatus(BinaryEnum.FALSE);
                    salary.setBonus(bonus);
                    salaryService.modify(salary);
                    salaryEmailList.add(SalaryEmail.builder()
                        .to(user.getEmail())
                        .name(user.getName())
                        .subject("叮咚！" + user.getName() + "，您的" + time.getMonth() + "月薪资账单出炉啦！")
                        .salary(salary)
                        .build());
                    List<Long> rewardIds = rewards.stream().map(RewardDTO::getId).collect(Collectors.toList());
                    rewardService.modifyStatus(rewardIds, BinaryEnum.FALSE);
                });
        });
        mailService.batchSendSimpleMail(salaryEmailList);
    }

    /**
     * SalaryVO生成器
     *
     * @param dto SalaryDTO
     * @return SalaryVO
     */
    private SalaryVO generateVO(SalaryDTO dto) {
        return SalaryVO.builder()
            .id(dto.getId())
            .name(userService.queryName(dto.getUserId()))
            .basicSalary(dto.getBasicSalary())
            .absenceDay(dto.getAbsenceDay())
            .accumulationFundBase(dto.getAccumulationFundBase())
            .accumulationFundPercentage(dto.getAccumulationFundPercentage())
            .bonus(dto.getBonus())
            .finalSalary(dto.getFinalSalary())
            .individualTax(dto.getIndividualTax())
            .lunchSalary(dto.getLunchSalary())
            .medicalBase(dto.getMedicalBase())
            .medicalPercentage(dto.getMedicalPercentage())
            .pensionBase(dto.getPensionBase())
            .pensionPercentage(dto.getPensionPercentage())
            .salaryTime(dto.getSalaryTime())
            .trafficSalary(dto.getTrafficSalary())
            .workTime(dto.getWorkTime())
            .build();
    }
}
